﻿#Region "File Description"
'-----------------------------------------------------------------------------
' HeightMapInfo.vb
'
' Translated from example by
' Microsoft XNA Community Game Platform
' Copyright (C) Microsoft Corporation. All rights reserved.
'-----------------------------------------------------------------------------
#End Region

#Region " Imports Statements "
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports Microsoft.Xna.Framework.Content
Imports Microsoft.Xna.Framework
#End Region ' Imports Statements

''' <summary>
''' HeightMapInfo is a collection of data about the heightmap. It includes
''' information about how high the terrain is, and how far apart each vertex is.
''' It also has several functions to get information about the heightmap, including
''' its height at different points, and whether a point is on the heightmap.
''' It is the runtime equivalent of HeightMapInfoContent.
''' </summary>
Public Class HeightMapInfo

#Region " Fields "

    ''' <summary>
    ''' TerrainScale is the distance between each entry in the Height property.
    ''' For example, if TerrainScale is 30, Height[0,0] and Height[1,0] are 30
    ''' units apart.
    ''' </summary>
    Private mTerrainScale As Single

    ''' <summary>
    ''' This 2D array of floats tells us the height that each position in the
    ''' heightmap is.
    ''' </summary>
    Private mHeights As Single(,)

    ''' <summary>
    ''' The position of the heightmap's -x, -z corner, in worldspace.
    ''' </summary>
    Private mHeightmapPosition As Vector3

    ''' <summary>
    ''' The total width of the heightmap, including terrainscale.
    ''' </summary>
    Private mHeightmapWidth As Single

    ''' <summary>
    ''' The total height of the height map, including terrainscale.
    ''' </summary>
    Private mHeightmapHeight As Single

#End Region ' Fields


    ''' <remarks>The constructor will initialize all of the member variables.</remarks>
    Public Sub New(ByVal heights As Single(,), ByVal terrainScale As Single)
        If heights Is Nothing Then
            Throw New ArgumentNullException("heights")
        End If

        Me.mTerrainScale = terrainScale
        Me.mHeights = heights

        mHeightmapWidth = (heights.GetLength(0) - 1) * terrainScale
        mHeightmapHeight = (heights.GetLength(1) - 1) * terrainScale

        mHeightmapPosition.X = -(heights.GetLength(0) - 1) / 2 * terrainScale
        mHeightmapPosition.Z = -(heights.GetLength(1) - 1) / 2 * terrainScale
    End Sub


    ''' <summary>
    ''' This function takes in a position, and tells whether or not the position is
    ''' on the heightmap.
    ''' </summary>
    Public Function IsOnHeightmap(ByVal position As Vector3) As Boolean
        ' first we'll figure out where on the heightmap "position" is...
        Dim positionOnHeightmap As Vector3 = position - mHeightmapPosition

        ' ... and then check to see if that value goes outside the bounds of the
        ' heightmap.
        Return (positionOnHeightmap.X >= 0 AndAlso _
                positionOnHeightmap.X < mHeightmapWidth AndAlso _
                positionOnHeightmap.Z >= 0 AndAlso _
                positionOnHeightmap.Z < mHeightmapHeight)
    End Function

    ''' <summary>
    ''' This function takes in a position, and returns the heightmap's height at that
    ''' point. Be careful - this function will throw an IndexOutOfRangeException if
    ''' position isn't on the heightmap!
    ''' </summary>
    Public Function GetHeight(ByVal position As Vector3) As Single
        ' the first thing we need to do is figure out where on the heightmap
        ' "position" is. This'll make the math much simpler later.
        Dim positionOnHeightmap As Vector3 = position - mHeightmapPosition

        ' we'll use integer division to figure out where in the "heights" array
        ' positionOnHeightmap is. Remember that integer division always rounds
        ' down, so that the result of these divisions is the indices of the "upper
        ' left" of the 4 corners of that cell.
        ' GDA - in VB, converting to an integer does NOT round down.
        Dim left As Integer, top As Integer, right As Integer, bottom As Integer
        left = Math.Floor(positionOnHeightmap.X / mTerrainScale)
        top = Math.Floor(positionOnHeightmap.Z / mTerrainScale)
        right = left + 1
        bottom = top + 1
        ' next, we'll use modulus to find out how far away we are from the upper
        ' left corner of the cell. Mod will give us a value from 0 to terrainScale,
        ' which we then divide by terrainScale to normalize 0 to 1.
        Dim xNormalized As Single = (positionOnHeightmap.X Mod mTerrainScale) / mTerrainScale
        Dim zNormalized As Single = (positionOnHeightmap.Z Mod mTerrainScale) / mTerrainScale

        ' Now that we've calculated the indices of the corners of our cell, and
        ' where we are in that cell, we'll use bilinear interpolation to calculuate
        ' our height. This process is best explained with a diagram, so please see
        ' the accompanying doc for more information.
        ' First, calculate the heights on the bottom and top edge of our cell by
        ' interpolating from the left and right sides.
        Dim topHeight As Single = MathHelper.Lerp( _
                                  mHeights(left, top), _
                                  mHeights(right, top), xNormalized)

        Dim bottomHeight As Single = MathHelper.Lerp(mHeights(left, bottom), _
                                                     mHeights(right, bottom), _
                                                     xNormalized)

        ' next, interpolate between those two values to calculate the height at our
        ' position.
        Return MathHelper.Lerp(topHeight, bottomHeight, zNormalized)
    End Function
End Class